﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using NEmu;

namespace Super64.Core.CPU.MIPS
{
    public static class Disassembler
    {
        private static Dictionary<InstructionType, Dictionary<byte, MipsOpcode>> s_OpcodeTableLookup;
        private static Dictionary<byte, MipsOpcode> s_CP0RegisterOps;
        private static Dictionary<byte, MipsOpcode> s_CP0RegisterRSOps;
        private static Dictionary<byte, MipsOpcode> s_FPURegisterOps;
        private static Dictionary<byte, MipsOpcode> s_FPURegisterRSOps;
        private static Dictionary<byte, MipsOpcode> s_ImmediateOps;
        private static Dictionary<byte, MipsOpcode> s_JumpOps;
        private static Dictionary<byte, MipsOpcode> s_RegisterOps;
        private static Dictionary<byte, MipsOpcode> s_RegisterRTOps;
        public const byte OPCODE_CP0 = 16;
        public const byte OPCODE_FPU = 17;
        public const byte OPCODE_RT = 1;

        static Disassembler()
        {
            s_CP0RegisterOps = new Dictionary<byte, MipsOpcode>();
            s_CP0RegisterRSOps = new Dictionary<byte, MipsOpcode>();
            s_FPURegisterOps = new Dictionary<byte, MipsOpcode>();
            s_FPURegisterRSOps = new Dictionary<byte, MipsOpcode>();
            s_ImmediateOps = new Dictionary<byte, MipsOpcode>();
            s_JumpOps = new Dictionary<byte, MipsOpcode>();
            s_OpcodeTableLookup = new Dictionary<InstructionType, Dictionary<byte, MipsOpcode>>();
            s_RegisterOps = new Dictionary<byte, MipsOpcode>();
            s_RegisterRTOps = new Dictionary<byte, MipsOpcode>();

            foreach (MipsOpcode opcode in Enum.GetValues(typeof(MipsOpcode)))
            {
                object[] attributes = Tools.GetEnumAttributes<MipsOpcode>(opcode, typeof(OpcodeMeta));

                if (attributes.Length > 0)
                {
                    InstructionType type = ((OpcodeMeta)attributes[0]).Type;
                    byte opNum = ((OpcodeMeta)attributes[0]).OpNumber;

                    switch (type)
                    {
                        case InstructionType.CP0Register: s_CP0RegisterOps.Add(opNum, opcode); break;
                        case InstructionType.CP0RegisterRS: s_CP0RegisterRSOps.Add(opNum, opcode); break;
                        case InstructionType.FPURegister: s_FPURegisterOps.Add(opNum, opcode); break;
                        case InstructionType.FPURegisterRS: s_FPURegisterRSOps.Add(opNum, opcode); break;
                        case InstructionType.Immediate: s_ImmediateOps.Add(opNum, opcode); break;
                        case InstructionType.Jump: s_JumpOps.Add(opNum, opcode); break;
                        case InstructionType.Register: s_RegisterOps.Add(opNum, opcode); break;
                        case InstructionType.RegisterRT: s_RegisterRTOps.Add(opNum, opcode); break;
                        case InstructionType.Unknown: continue;
                        default: continue;
                    }
                }
            }

            s_OpcodeTableLookup.Add(InstructionType.CP0Register, s_CP0RegisterOps);
            s_OpcodeTableLookup.Add(InstructionType.CP0RegisterRS, s_CP0RegisterRSOps);
            s_OpcodeTableLookup.Add(InstructionType.FPURegister, s_FPURegisterOps);
            s_OpcodeTableLookup.Add(InstructionType.FPURegisterRS, s_FPURegisterRSOps);
            s_OpcodeTableLookup.Add(InstructionType.Immediate, s_ImmediateOps);
            s_OpcodeTableLookup.Add(InstructionType.Jump, s_JumpOps);
            s_OpcodeTableLookup.Add(InstructionType.Register, s_RegisterOps);
            s_OpcodeTableLookup.Add(InstructionType.RegisterRT, s_RegisterRTOps);
        }

        public static MipsOpcode GetOpcode(MipsInstruction inst)
        {
            MipsOpcode op;
            InstructionType type;
            Dictionary<byte, MipsOpcode> opTable;
            byte opNum;

            if (inst.RawInstruction == 0)
            {
                return MipsOpcode.NOP;
            }


            // Co-Processor 0 Opcodes
            if (inst.Opcode == OPCODE_CP0)
            {
                if (inst.Funct > 0)
                {
                    type = InstructionType.CP0Register;
                    opNum = inst.Funct;
                }
                else
                {
                    type = InstructionType.CP0RegisterRS;
                    opNum = inst.RS;
                }
            }
            else if (inst.Opcode == OPCODE_FPU) // FPU Opcodes
            {
                if (inst.Funct > 0)
                {
                    type = InstructionType.FPURegister;
                    opNum = inst.Funct;
                }
                else
                {
                    type = InstructionType.FPURegisterRS;
                    opNum = inst.RS;
                }
            }
            else if (inst.Opcode == OPCODE_RT)
            {
                type = InstructionType.RegisterRT;
                opNum = inst.RT;
            }
            else if (inst.Opcode == (byte)MipsOpcode.J || inst.Opcode == (byte)MipsOpcode.JAL)
            {
                type = InstructionType.Jump;
                opNum = inst.Opcode;
            }
            else
            {
                if (inst.Opcode < 1)
                {
                    type = InstructionType.Register;
                    opNum = inst.Funct;
                }
                else
                {
                    type = InstructionType.Immediate;
                    opNum = inst.Opcode;
                }
            }

            if (s_OpcodeTableLookup.TryGetValue(type, out opTable))
            {
                if (opTable.TryGetValue(opNum, out op))
                {
                    return op;
                }
                else
                {
                    return MipsOpcode.UNKNOWN;
                }
            }
            else
            {
                return MipsOpcode.UNKNOWN;
            }
        }
    }
}
